package com.na.proxy;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class ClientSession implements Task {

	private Socket socket;
	private InputStream in;
	private OutputStream out;
	private boolean connected;
	
	private Socket conn; // connect with the dest server
	private InputStream forwardIn;
	private OutputStream forwardOut;
	
	private TaskExecutor executor;
	
	// Header格式:
	// key: value[CR]
	// key: value[CR]
	// ...
	// [LF][CR][LF]
	private static final byte CR = '\r'; // 回车0x0d 13
	private static final byte LF = '\n'; // 换行0x0a 10
	private static final byte[] HEADER_END = new byte[]{
		LF, CR, LF
	};
	
	private static final String HEADER_HOST_KEY = "Host: ";
	private static final int HTTP_DEFAULT_PORT = 80;
	
	/**
	 * Forward the data from client to server(or from server to client)
	 * @author yang.li
	 *
	 */
	class Forwarder implements Task {
		private InputStream in;
		private OutputStream out;
		Forwarder(InputStream in, OutputStream out) {
			this.in = in;
			this.out = out;
		}
		
		@Override
		public void run() {
			byte[] buffer = new byte[512];
			int len;
			while (connected) {
				try {
					if ((len = in.read(buffer, 0, buffer.length)) != -1) {
						out.write(buffer, 0, len);
						out.flush();
					}
				} catch (IOException e) {
					Log.getDefault().error(e, "Writter run error");
					ClientSession.this.shutdown();
				}
			}
		}

		@Override
		public void shutdown() {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
				}
			}
			if (out != null) {
				try {
					out.flush();
					out.close();
				} catch (IOException e) {}
			}
		}
		
	}
	
	public ClientSession(Socket socket) {
		this.socket = socket;
		executor = new TaskExecutor();
	}
	
	@Override
	public void run() {
		connected = true;
		try {
			in = socket.getInputStream();
			out = socket.getOutputStream();
			readHeaders();
			runWritter();
			runReader();
		} catch (Exception e) {
			e.printStackTrace();
			shutdown();
		}
	}
	
	private void readHeaders() {
		try {
			// Header格式:
			// key: value[CR]
			// key: value[CR]
			// ...
			// [LF][CR][LF]
			ByteArrayBuffer arr = new ByteArrayBuffer(128);
			byte[] buffer = new byte[128];
			int len;
			while ((len = in.read(buffer, 0, buffer.length)) != -1) {
				arr.write(buffer, 0, len);
				if (headerReadEnd(arr.getArray(), arr.size())) {
					break;
				}
			}
			String headers = new String(arr.toByteArray(), "utf-8");
			Log.getDefault().info("Headers:\n" + headers);
			Host dest = getHostFromHeaders(headers);
			conn = new Socket(dest.host, dest.port);
			forwardOut = conn.getOutputStream();
			forwardIn = conn.getInputStream();
			// write headers to http server
			forwardOut.write(arr.getArray(), 0, arr.size());
			forwardOut.flush();
			Log.getDefault().info("connected[" + dest.toString() + "]");
			// parse headers
//			if ((value = conn.getRequestProperty("Content-Length")) != null) {
//				int len = Integer.parseInt(value);
//				byte[] buffer = new byte[len];
//				in.read(buffer);
//				forwardOut = conn.getOutputStream();
//				forwardOut.write(buffer);
//				forwardOut.flush();
//			}
			
		} catch (IOException e) {
			Log.getDefault().error(e, "readHeaders error");
			shutdown();
		}
	}
	
	private static boolean headerReadEnd(byte[] buffer, int bufferLen) {
		int len = HEADER_END.length;
		if (bufferLen < len) {
			return false;
		}
		for (int i = 0; i < len; i++) {
			if (HEADER_END[i] != buffer[bufferLen - len + i]) {
				return false;
			}
		}
		return true;
	}
	
	private static Host getHostFromHeaders(String headers) throws IOException {
		int dot = headers.indexOf(HEADER_HOST_KEY);
		if (dot < 0) {
			throw new IOException("parse header error\n" + headers);
		}
		int end = headers.indexOf(CR, dot + HEADER_HOST_KEY.length());
		if (end < 0) {
			throw new IOException("parse header error\n" + headers);
		}
		String host = headers.substring(dot + HEADER_HOST_KEY.length(), end);
		dot = host.indexOf(':');
		int port = HTTP_DEFAULT_PORT;
		if (dot != -1) {
			port = Integer.parseInt(host.substring(dot + 1));
			host = host.substring(0, dot);
		}
		return new Host(host, port);
	}
	
	/**
	 * Read from client, and write to http server
	 */
	private void runWritter() {
		executor.runTask(new Forwarder(in, forwardOut));
	}
	
	/**
	 * Read from http server, and write to client
	 */
	private void runReader() {
		executor.runTask(new Forwarder(forwardIn, out));
	}

	@Override
	public void shutdown() {
		connected = false;
		executor.shutdown();
		if (conn != null) {
			try {
				conn.close();
			} catch (IOException e) {
			}
		}
		if (socket != null) {
			try {
				socket.close();
				socket = null;
			} catch (IOException e) {
			}
		}
		Log.getDefault().info("ClientSession shutdown");
	}

}
